/**********************************************************************
*file:程序开发驱动文件
*author:残梦
*versions:V1.0
*date:2025.1.20
*note:
**********************************************************************/
#include "common_driver.h"
#ifdef dCommonFunctionEnable_STM32_FreeRTOS
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os.h"
#endif

#ifdef dCommonFunctionEnable_Base
/****************************************************
@function:计算数据的拟合系数
@param:*pA,*pB--系数
		x[],y[]--数据源
		dataSize--数据个数
@return:void
@note:拟合曲线y=Ax+B
****************************************************/
void Common_LinearFitCoefficient(double *pA,double *pB,double x[],double y[],uint32_t dataSize)
{
	uint32_t i= 0;
	double AverX = 0.0f,AverY = 0.0f,a1 = 0.0f,a2 = 0.0f;

	if(dataSize == 0){*pA = *pB = 0.0;return;}
	else if(dataSize == 1){*pA = 0.0;*pB = y[0];return;}
	while(i < dataSize)	{AverX += x[i];AverY += y[i];i++;}
	AverX /= (double)(dataSize);AverY /= (double)(dataSize);

	a1 = a2 = 0.0f;
	for(i = 0;i < dataSize;i++)
	{
		a1 += (x[i] - AverX)*(y[i] - AverY);
		a2 += (x[i] - AverX)*(x[i] - AverX);
	}
	*pA = a1/a2;
	*pB = AverY - (*pA)*AverX;
}

/****************************************
@function:二分法查找target在数组pdata中的最相邻位置
@param:target--目标数据，pdata--源数据，len--源数据长度
@return:[0,len-1]
@note:
****************************************/
uint32_t Common_DichotomyFindPos(float target,float *pdata,uint32_t len)
{
	uint32_t pos = 0,posl = 0,posr = 0;
	uint8_t flag = 0;

	//for(uint32_t z = 0;z < len;z++){printf("[%d]=%f\n",z,*(pdata+z));}
	if(len <= 2){return 0;}
	//判定数据是否在区间外
	flag = (*(pdata + len -1) > *pdata)?1:0;
	if(flag == 1)//递增数据
	{
		if(target < *pdata)return 0;
		else if(target > *(pdata + len -1))return (len -1);
	}
	else
	{
		if(target > *pdata)return 0;
		else if(target < *(pdata + len -1))return (len -1);
	}

	uint32_t num = 0;
	//区间内的数据
	posl = 0;posr = len -1;
	while((posl != (posr-1)) && (posl != posr))
	{
		pos = (posr + posl)/2;
		if(flag == 1)
		{
			if(target < (*(pdata + pos))){posr = pos;}
			else{posl = pos;}
		}
		else
		{
			if(target > (*(pdata + pos))){posr = pos;}
			else{posl = pos;}
		}
		num++;
		//printf("%d [%d,%d]=[%f,%f]\n",num,posl,posr,*(pdata + posl),*(pdata + posr));
	}
	//printf("[pos,tar]=[%d,%f] num=%d\n",posl,target,num);
	return posl;
}

/****************************************
@function:生成正弦波数据点函数
@param:	
		bits:ADC位数;12bit=12,16bit=16
		VoltageMax:ADC输出电压上限,比如STM32最高输出3.3V,注意生成波形时stm32输出低于0.02V电压会容易DAC硬件失真
		NPoints       一个周期内的点数
		amplitude:交流幅值=最高电压与最低电压的差值;最小值0
		offset:偏移电压
        SineWaveTable 存放生成的数据点
@return:void
@note:注意(amplitude + offset) < VoltageMax
****************************************/
void Common_Generate_SineWave(uint8_t bits,float VoltageMax,uint16_t NPoints,float amplitude,float offset,uint16_t *SineWaveTable)
{
	uint16_t i = 0;
	double vadc = pow(2,bits);

	if((SineWaveTable == NULL) || ((amplitude + offset) > VoltageMax) || (NPoints == 0) || (bits == 0))return;
	amplitude = dCommon_Limit_Min(amplitude,0.0f) / 2.0f;
	for( i = 0;i < NPoints;i++)
	{
		*(SineWaveTable + i) = (uint16_t)(sin(2.0*dCommon_PI*i / NPoints) * amplitude * vadc / VoltageMax + offset * vadc / VoltageMax);
	}
}

/****************************************
@function:打印数组数据--十六进制
@param:message--输出显示提示信息
	pbuf--待打印的数据
	len--数据字节数
	separator:打印数据之间的分隔符，例如空格' '，逗号','等
	prefix：eTure--显示十六进制前缀0x;eFalse--不予显示前缀
@return:0--打印成功，-1--参数错误
@note:注意：结尾会携带一个分隔符
****************************************/
int32_t Common_Printf_HEX(uint8_t *message,uint8_t *pbuf, uint32_t len,uint8_t separator,bool prefix)
{
    uint32_t i = 0;

	if((pbuf == NULL) || (len == 0))return -1;
    if(message != NULL) dLOG("%s[%d]:",message,len);
    for(i = 0;i < len;i++)
    {
		if(prefix)dLOG("0x%02X%c",*(pbuf + i),separator);
		else dLOG("%02X%c",*(pbuf + i),separator);
    }
    dLOG("\r\n");
	return 0;
}

/****************************************
@function:打印数组数据--float
@param:message--输出显示提示信息
	pbuf--待打印的数据
	len--数据字节数
	separator:打印数据之间的分隔符，例如空格' '，逗号','等
	bits--小数点后显示有效位数，范围[0,6]
@return:0--打印成功，-1--参数错误
@note:显示时会四舍五入
	注意：结尾会携带一个分隔符
****************************************/
int32_t Common_Printf_Float(uint8_t *message,float *pbuf, uint32_t len,uint8_t separator,int8_t bits)
{
    uint32_t i = 0;
	char format[] = "%.6f%c";

	if((pbuf == NULL) || (len == 0))return -1;
	if(dCommon_Range_Check(bits,0,6))return -1;
    if(message != NULL) dLOG("%s[%d]:",message,len);
	format[2] = bits + '0';
    for(i = 0;i < len;i++)
    {
		dLOG(format,*(pbuf + i),separator);
    }
    dLOG("\r\n");
	return 0;
}

/****************************************
@function:打印数组数据--整数类型
@param:message--输出显示提示信息
	pbuf--待打印的数据
	len--数据字节数
	separator:打印数据之间的分隔符，例如空格' '，逗号','等
	type--数据类型：
		0-int8_t
		1-uint8_t
		2-int16_t
		3-uint16_t
		4-int32_t
		5-uint32_t
@return:0--打印成功，-1--参数错误
@note:注意：结尾会携带一个分隔符
此函数不会严格区负数，这只是个打印整数函数：    
	例如
	uint32_t data[3] = {50,-510000,20};
    Common_Printf_Integer("data",data,3,',',5);
	输出：data[3]:50,-510000,20,
	所以需要传入数据符合变量类型要求
****************************************/
int32_t Common_Printf_Integer(uint8_t *message,void *pbuf, uint32_t len,uint8_t separator,int8_t type)
{
    uint32_t i = 0;

	if((pbuf == NULL) || (len == 0))return -1;
	if(dCommon_Range_Check(type,0,5))return -1;
    if(message != NULL) dLOG("%s[%d]:",message,len);
    for(i = 0;i < len;i++)
    {
		switch(type)
		{
			case 0:dLOG("%d%c",*(((int8_t *)pbuf) + i),separator);break;
			case 1:dLOG("%d%c",*(((uint8_t *)pbuf) + i),separator);break;
			case 2:dLOG("%d%c",*(((int16_t *)pbuf) + i),separator);break;
			case 3:dLOG("%d%c",*(((uint16_t *)pbuf) + i),separator);break;
			case 4:dLOG("%d%c",*(((int32_t *)pbuf) + i),separator);break;
			case 5:dLOG("%d%c",*(((uint32_t *)pbuf) + i),separator);break;
			default:return -1;
		}
    }
    dLOG("\r\n");
	return 0;
}
#endif

#ifdef dCommonFunctionEnable_PID
/****************************************
@function:增量式PID算法
@param:	pid--Common_PID_Parameter_StructDef
		actual_val--当前采集值
		Min--输出限幅最小值
		Max--输出限幅最大值
@return:
@note:
****************************************/
float Common_PID_realize_increment(Common_PID_Parameter_StructDef *pid,float actual_val,int64_t Min,int64_t Max)
{
	/*计算目标值与实际值的误差*/
	pid->err=pid->target-actual_val;
	/*PID算法实现*/
	pid->actual += pid->Kp*(pid->err - pid->err_next)
					+ pid->Ki*pid->err
					+ pid->Kd*(pid->err - 2 * pid->err_next + pid->err_last);
	/*传递误差*/
	pid->err_last = pid->err_next;
	pid->err_next = pid->err;

	pid->actual = (pid->actual < Min)?Min:pid->actual;
	pid->actual = (pid->actual > Max)?Max:pid->actual;

   /*返回当前实际值*/
   return pid->actual;
}

/****************************************
@function:位置式PID算法
@param:	pid--Common_PID_Parameter_StructDef
		actual_val--当前采集值
		Min--输出限幅最小值
		Max--输出限幅最大值
@return:
@note:
****************************************/
float Common_PID_realize_location(Common_PID_Parameter_StructDef *pid,float actual_val,int64_t Min,int64_t Max)
{
	/*计算目标值与实际值的误差*/
	pid->err=pid->target-actual_val;
	/*误差累积*/
	pid->integral+=pid->err;
	/*PID算法实现*/
	pid->actual=pid->Kp*pid->err + pid->Ki*pid->integral + pid->Kd * (pid->err - pid->err_last);
	/*误差传递*/
	pid->err_last=pid->err;

	pid->actual = (pid->actual < Min)?Min:pid->actual;
	pid->actual = (pid->actual > Max)?Max:pid->actual;
	return pid->actual;
}
#endif

#ifdef dCommonFunctionEnable_LinearFitting
/**使用方法:
 * 校准数据写入方法：
    double x[2] = {0.0},y[2] = {0.0},a = 0.0,b = 0.0;
    float set = 0.0f,real = 0.0f;

	Common_LinearFitting_Init(p,初始化数据资源点数);
    while(x)//循环写入校准数据
    {
        sscanf(string,"%f\t%f\n",&set,&real);
        y[1] = y[0];y[0] = set/1000.0f;//set电压单位mV
        x[1] = x[0];x[0] = real;//实际电压单位V
        p->real[p->number] = real;
        if(p->number < 1){p->number++;continue;}
        Common_LinearFitCoefficient(&a,&b,x,y,2);
        p->a[p->number - 1] = a;
        p->b[p->number - 1] = b;
        if(p->number++ > p->length)break;
    }
    if(p->number > 2)//处理最后一行数据
    {
        p->a[p->number - 1] = p->a[p->number - 2];
        p->b[p->number - 1] = p->b[p->number - 2];
    }
    else if(p->number < 2)//数据过少
    {
        p->number = 0;
        xReturn = -1;
    }
 * 
 * 
 * ****/

/****************************************
@function:线性拟合资源初始化
@param:p--待初始化的资源句柄
    number--校准数据点数
@return:0--成功，-1--失败
@note:内部会动态分配资源，再也不用需要调用Common_LinearFitting_Deinit()释放此内存
****************************************/
int32_t Common_LinearFitting_Init(Common_LinearFitting_Parameter_StructDef *p,uint32_t number)
{
    if(p == NULL)return -1;
    p->length = 0;
    p->number = 0;
	p->real = (float *)malloc(number*sizeof(float));
	if (p->real == NULL)return -1;
	p->a = (float *)malloc(number*sizeof(float));
	if (p->a == NULL){free(p->real);return -1;}
	p->b = (float *)malloc(number*sizeof(float));
	if (p->b == NULL)
	{
		free(p->real);
        free(p->a);
		return -1;
	}
    p->length = number;
    return 0;
}

/****************************************
@function:线性拟合资源销毁
@param:p--线性拟合句柄
@return:void
@note:
****************************************/
void Common_LinearFitting_Deinit(Common_LinearFitting_Parameter_StructDef *p)
{
    if(p == NULL)return;
    if(p->real != NULL)free(p->real);
    if(p->a != NULL)free(p->a);
    if(p->b != NULL)free(p->b);
    p->length = 0;
    p->number = 0;
}

/****************************************
@function:线性拟合参数清除
@param:p--线性拟合句柄
@return:void
@note:
****************************************/
void Common_LinearFitting_Clear(Common_LinearFitting_Parameter_StructDef *p)
{
    if(p == NULL)return;
    p->number = 0;
}

/****************************************
@function:线性拟合计算
@param:
    p--线性拟合句柄
    real--待拟合的值
@return:线性拟合后的值
@note:采用y=ax+b
****************************************/
float Common_LinearFitting_Calculate(Common_LinearFitting_Parameter_StructDef p,float real)
{
    uint32_t pos = 0;

    if(p.number == 0) return real;//没有拟合原始数据
    pos = Common_DichotomyFindPos(real,p.real,p.number);
    real = real * p.a[pos] + p.b[pos];
    //dLOG("num=%d real=%.3f pos[%d] [a,b]=[%.6f,%.6f]\r\n",\
    //  p.number,real,pos,p.a[pos],p.b[pos]);
    return real;
}

#endif

#ifdef dCommonFunctionEnable_STM32_Base
#include "usart.h"

/******************************
@function:printf打印使用
@param:
@return:
@remark:
******************************/
// int fputc(int ch,FILE *f)
// {
// 	unsigned char temp[1] = {ch};
// 	HAL_UART_Transmit(&huart1,temp,1,2);
// 	return(ch);
// }

/****************************************
@function:USB断开连接
@param:void
@return:void
@note:此函数使用在USB外设初始化前，模拟USB重新插拔的过程，使其触发重新挂载USB
****************************************/
void Common_USB_Disconnet(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  __HAL_RCC_GPIOA_CLK_ENABLE();
  GPIO_InitStruct.Pin = GPIO_PIN_12;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);
  HAL_Delay(13);//USB断开后需保持10ms确保主机检测到断开
}

/****************************************
@function:STM32系统重启
@param:void
@return:void
@note:此函数需配合Common_SystemReset_Monitor()一起使用
****************************************/
static bool com_SystemReset = false;//系统重启变量
void Common_SystemReset(void)
{
    com_SystemReset = true;
}

/****************************************
@function:系统重启监视
@param:promptly--立即重启：False--等待Common_SystemReset()设置后才重启，True--立即重启
@return:void
@note:中断中使用无效，Common_SystemReset()设置后此函数内会执行重启系统，应放在非中断中循环检测
****************************************/
void Common_SystemReset_Monitor(bool promptly)
{
    if((com_SystemReset == false) && (promptly == false))return;
    dLOG("System Reset...\r\n");
    __set_FAULTMASK(1);//close all interrupt
    NVIC_SystemReset();//reset
}

#endif

#ifdef dCommonFunctionEnable_STM32_TIM
/****************************************
@function:设置定时器tim通道Channel的PWM，并输出
@param: tim--定时器
        Channel--通道:TIM_CHANNEL_x
        Pulse--脉冲宽度,限制范围[Min,Max]
@return:0--成功，-1--参数错误
@note:此函数不负责开启PWM，只是能改占空比的值；开启PWM需使用启动函数HAL_TIM_PWM_Start(tim,Channel);
****************************************/
int32_t Common_PWM_Channe_Set(TIM_HandleTypeDef *tim,uint32_t Channel,uint32_t Pulse,uint32_t Min,uint32_t Max)
{
	if(tim == NULL)return -1;
    Pulse = dCommon_Limit_Range(Pulse,Min,Max);
    __HAL_TIM_SET_COMPARE(tim, Channel, Pulse);
	return 0;
}

/****************************************
@function:重新配置定时器的分频系数+周期数
@param:tim--定时器句柄
	Prescaler--分频系数，范围[1,65535]
	Period--周期，范围[1,65535]
	中断周期=频率 / Prescaler / Period
@return:0--成功，-1--参数错误
@note:
****************************************/
int32_t Common_TIM_Configurate(TIM_HandleTypeDef *tim,uint16_t Prescaler,uint16_t Period)
{
	if(tim == NULL) return -1;
    tim->Init.Prescaler = dCommon_Limit_Min(Prescaler,1) - 1;
	#if 1
	__HAL_TIM_SET_AUTORELOAD(tim, Period);//这个函数会更新tim->Init.Period
	__HAL_TIM_SET_PRESCALER(tim, tim->Init.Prescaler);
	#else
	HAL_TIM_Base_Stop(tim);
	if (HAL_TIM_Base_Init(tim) != HAL_OK){dLOG_FUNC_LINE();return;}
	#endif
	return 0;
}

/****************************************************
@function:计算定时器指定频率的分频系数和周期
@param:frequency--目标频率
	clock_frequency--定时器挂载的时钟频率
	prescaler--分频系数
	period--周期
@return:0--计算成功
	-1--参数错误
@note:
****************************************************/
int32_t Common_TIM_Frequency_Calculate(float frequency,uint32_t clock_frequency,uint32_t *prescaler,uint32_t *period)
{
    *prescaler = 1;
    *period = 65536;

    while(*period > 65535)
    {
        *period = (uint32_t )((float )clock_frequency / frequency / (float )(*prescaler) + 0.5f);
        *prescaler *= 10;
    }
    *prescaler /= 10;

    //dLOG("frequency=%.2f prescaler=%d period=%d\r\n",((float)clock_frequency / (float )*prescaler / (float )*period),*prescaler,*period);
    return 0;
}

/****************************************
@function:定时器频率设置
@param:tim--定时器句柄
	frequency--频率
	clock_frequency--定时器挂载的时钟频率
@return:0--成功，-1--失败
@note:此函数不予启动定时器
****************************************/
int32_t Common_TIM_Frequency_Set(TIM_HandleTypeDef *tim,float frequency,uint32_t clock_frequency)
{
	uint32_t prescaler = 0,period = 0;

	if(tim == NULL)return -1;
	if(Common_TIM_Frequency_Calculate(frequency,clock_frequency,&prescaler,&period) < 0)return -1;
	Common_TIM_Configurate(tim,prescaler,period);
	return 0;
}

#endif

#ifdef dCommonFunctionEnable_STM32_FreeRTOS
/****************************************
@function:监视任务堆栈剩余空间，低于size则报告
@param:handle--任务句柄
  TaskName--线程名字
  size--堆栈下限值
@return:void
@note:需要FreeRTOS开启uxTaskGetStackHighWaterMark功能
****************************************/
void Common_FreeRTOS_TaskStack_Monitor(void *TaskHandle,char *TaskName,uint32_t size)
{
	
  uint32_t residue = 0;
  residue = uxTaskGetStackHighWaterMark( ((osThreadId )(TaskHandle)) );
  if(residue >= size)return;
  dLOG("%s:Residual stack %u[word]\r\n",TaskName,residue);
}

/****************************************
@function:显示堆栈、CPU使用率
@param:void
@return:void
@note:当任务较多时应增加函数中CPU_RunInfo数组大小，数组较小会触发HardFault_Handler()
	并且调用此函数的任务堆栈大小不得低于此数组大小
****************************************/
void Common_FreeRTOS_CPU_Usage_Display(void)
{
	uint8_t CPU_RunInfo[400] = {0};//保存任务运行时间信息,当任务较多时应增加此数组大小

    vTaskList((char *)CPU_RunInfo);  //获取任务运行时间信息
    dLOG("---------------------------------------------\r\n");
    dLOG("Name Status Priority ResidualStack SequenceNumber\r\n");//("任务名      任务状态 优先级   剩余栈 任务序号\r\n");
    dLOG("%s", CPU_RunInfo);
    dLOG("---------------------------------------------\r\n");

    vTaskGetRunTimeStats((char *)CPU_RunInfo);
    dLOG("Name RunCount UsageRate\r\n");//("任务名       运行计数         使用率\r\n");
    dLOG("%s", CPU_RunInfo);
    dLOG("---------------------------------------------\r\n");
}

#endif
